/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.aplink.generic.google.datetimepicker.date;

import java.util.Calendar;
import java.util.HashMap;

import android.annotation.SuppressLint;
import android.content.Context;
import android.text.format.Time;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView.LayoutParams;
import android.widget.BaseAdapter;

import com.aplink.generic.google.datetimepicker.date.MonthView.OnDayClickListener;

/**
 * An adapter for a list of {@link MonthView} items.
 */
public abstract class MonthAdapter extends BaseAdapter implements
        OnDayClickListener {

	/**
	 * A convenience class to represent a specific date.
	 */
	public static class CalendarDay {
		private Calendar calendar;
		int day;
		int month;
		private Time time;
		int year;

		public CalendarDay() {
			setTime(System.currentTimeMillis());
		}

		public CalendarDay(final Calendar calendar) {
			this.year = calendar.get(Calendar.YEAR);
			this.month = calendar.get(Calendar.MONTH);
			this.day = calendar.get(Calendar.DAY_OF_MONTH);
		}

		public CalendarDay(final int year, final int month, final int day) {
			setDay(year, month, day);
		}

		public CalendarDay(final long timeInMillis) {
			setTime(timeInMillis);
		}

		public void set(final CalendarDay date) {
			this.year = date.year;
			this.month = date.month;
			this.day = date.day;
		}

		public void setDay(final int year, final int month, final int day) {
			this.year = year;
			this.month = month;
			this.day = day;
		}

		public synchronized void setJulianDay(final int julianDay) {
			if (this.time == null) {
				this.time = new Time();
			}
			this.time.setJulianDay(julianDay);
			setTime(this.time.toMillis(false));
		}

		private void setTime(final long timeInMillis) {
			if (this.calendar == null) {
				this.calendar = Calendar.getInstance();
			}
			this.calendar.setTimeInMillis(timeInMillis);
			this.month = this.calendar.get(Calendar.MONTH);
			this.year = this.calendar.get(Calendar.YEAR);
			this.day = this.calendar.get(Calendar.DAY_OF_MONTH);
		}
	}

	protected static final int MONTHS_IN_YEAR = 12;

	protected static int WEEK_7_OVERHANG_HEIGHT = 7;

	private final Context mContext;
	private final DatePickerController mController;

	private CalendarDay mSelectedDay;

	public MonthAdapter(final Context context,
			final DatePickerController controller) {
		this.mContext = context;
		this.mController = controller;
		init();
		setSelectedDay(this.mController.getSelectedDay());
	}

	public abstract MonthView createMonthView(Context context);

	@Override
	public int getCount() {
		return ((this.mController.getMaxYear() - this.mController.getMinYear()) + 1)
				* MonthAdapter.MONTHS_IN_YEAR;
	}

	@Override
	public Object getItem(final int position) {
		return null;
	}

	@Override
	public long getItemId(final int position) {
		return position;
	}

	public CalendarDay getSelectedDay() {
		return this.mSelectedDay;
	}

	@SuppressLint("NewApi")
	@SuppressWarnings("unchecked")
	@Override
	public View getView(final int position, final View convertView,
			final ViewGroup parent) {
		MonthView v;
		HashMap<String, Integer> drawingParams = null;
		if (convertView != null) {
			v = (MonthView) convertView;
			// We store the drawing parameters in the view so it can be recycled
			drawingParams = (HashMap<String, Integer>) v.getTag();
		} else {
			v = createMonthView(this.mContext);
			// Set up the new view
			final LayoutParams params = new LayoutParams(
					android.view.ViewGroup.LayoutParams.MATCH_PARENT,
					android.view.ViewGroup.LayoutParams.MATCH_PARENT);
			v.setLayoutParams(params);
			v.setClickable(true);
			v.setOnDayClickListener(this);
		}
		if (drawingParams == null) {
			drawingParams = new HashMap<String, Integer>();
		}
		drawingParams.clear();

		final int month = position % MonthAdapter.MONTHS_IN_YEAR;
		final int year = (position / MonthAdapter.MONTHS_IN_YEAR)
				+ this.mController.getMinYear();

		int selectedDay = -1;
		if (isSelectedDayInMonth(year, month)) {
			selectedDay = this.mSelectedDay.day;
		}

		// Invokes requestLayout() to ensure that the recycled view is set with
		// the appropriate
		// height/number of weeks before being displayed.
		v.reuse();

		drawingParams.put(MonthView.VIEW_PARAMS_SELECTED_DAY, selectedDay);
		drawingParams.put(MonthView.VIEW_PARAMS_YEAR, year);
		drawingParams.put(MonthView.VIEW_PARAMS_MONTH, month);
		drawingParams.put(MonthView.VIEW_PARAMS_WEEK_START,
				this.mController.getFirstDayOfWeek());
		v.setMonthParams(drawingParams);
		v.invalidate();
		return v;
	}

	@Override
	public boolean hasStableIds() {
		return true;
	}

	/**
	 * Set up the gesture detector and selected time
	 */
	protected void init() {
		this.mSelectedDay = new CalendarDay(System.currentTimeMillis());
	}

	private boolean isSelectedDayInMonth(final int year, final int month) {
		return (this.mSelectedDay.year == year)
				&& (this.mSelectedDay.month == month);
	}

	@Override
	public void onDayClick(final MonthView view, final CalendarDay day) {
		if (day != null) {
			onDayTapped(day);
		}
	}

	/**
	 * Maintains the same hour/min/sec but moves the day to the tapped day.
	 *
	 * @param day
	 *            The day that was tapped
	 */
	protected void onDayTapped(final CalendarDay day) {
		this.mController.tryVibrate();
		this.mController.onDayOfMonthSelected(day.year, day.month, day.day);
		setSelectedDay(day);
	}

	/**
	 * Updates the selected day and related parameters.
	 *
	 * @param day
	 *            The day to highlight
	 */
	public void setSelectedDay(final CalendarDay day) {
		this.mSelectedDay = day;
		notifyDataSetChanged();
	}
}
